<template>
  <view w-screen overflow-hidden relative :style="{ height: pageHeight + 'px' }">
    <actions @createSession="createChat" :aiInfo="aiInfo" @openSound="openSound"></actions>
    <scroll-view
      scroll-y
      enhanced
      :show-scrollbar="false"
      :style="{ height: chatHeight + 'px' }"
      w-full
      px-40rpx
      box-border
      :scroll-into-view="scrollIntoView"
      :scroll-with-animation="true">
      <view w-full flex justify-center pt-40rpx pb-20rpx>
        <image :src="aiInfo.headUrl" w-300rpx h-300rpx></image>
      </view>
      <view mt-20rpx>
        <!-- <view text-24rpx text="#5F6062" mb-20rpx>AI</view> -->
        <view class="ai-card" inline-block max-w-630rpx box-border px-36rpx py-20rpx text-28rpx>
          {{ aiInfo.prologue }}
        </view>
      </view>
      <template v-for="(item, index) in chatList" :key="index">
        <view v-if="item.role === 'assistant'" mt-40rpx :id="'msg_' + item.chat_id">
          <!-- <view text-24rpx text="#5F6062" mb-20rpx>AI</view> -->
          <view class="ai-card" inline-block max-w-630rpx box-border px-36rpx py-20rpx text-28rpx>
            <text v-if="item.loading">
              <nut-icon name="loading1"></nut-icon>
            </text>
            <nut-button v-if="item.success === '0'" plain type="primary" size="small" @click="restartSend">
              重新生成
            </nut-button>
            <rich-text :nodes="renderMarkdown(item.content)"></rich-text>
          </view>
        </view>
        <view v-else class="flex flex-col items-end" mt-40rpx :id="'msg_' + item.chat_id">
          <!-- <view class="mine-nick" text-24rpx text="#5F6062" mb-20rpx>我的昵称</view> -->
          <view class="mine-card" inline-block max-w-630rpx box-border px-36rpx py-20rpx text-28rpx>
            <text v-if="item.loading">
              <nut-icon name="loading1"></nut-icon>
            </text>
            <text v-if="item.success === '0'">语音发送失败, 请重试</text>
            {{ item.content }}
          </view>
        </view>
      </template>
      <view :id="'bottom_1'"></view>
      <view :id="'bottom_2'"></view>
      <view :id="'bottom_3'"></view>
      <view :id="'bottom_4'"></view>
      <view :id="'bottom_5'"></view>
      <view :id="'bottom_6'"></view>
      <view :id="'bottom_7'"></view>
      <view :id="'bottom_8'"></view>
      <view :id="'bottom_9'"></view>
      <view :id="'bottom_10'"></view>
    </scroll-view>
    <voiceBtn
      v-if="aiInfo.isShowOpenSound"
      v-show="voiceOrInput"
      @changeInput="voiceOrInput = !voiceOrInput"
      @sendVoice="sendVoice"
      @sendVoiceSuccess="sendVoiceSuccess"
      @sendVoiceError="sendVoiceError"
      :authorization="authorization" :aiAnswerComplete="aiAnswerComplete"></voiceBtn>
    <view
      v-show="!voiceOrInput"
      w-670rpx
      min-h-100rpx
      rounded-20rpx
      flex
      items-center
      justify-between
      px-40rpx
      box-border
      py-24rpx
      ml-40rpx
      mt-14rpx
      bg="#fff"
      style="box-shadow: 0rpx 2rpx 8rpx 2rpx rgba(168, 109, 0, 0.25)">
      <input
        type="text"
        v-model="textareaVal"
        placeholder="发消息......"
        w-480rpx
        confirm-type="send"
        cursor-spacing="20"
        @confirm="sendMsg" />
      <image
        v-if="aiInfo.isShowOpenSound"
        w-40rpx
        h-50rpx
        src="@/pages/funcPages/static/aiChat/voice.png"
        @click.stop="voiceOrInput = !voiceOrInput"></image>
    </view>
  </view>

  <tabbar></tabbar>
</template>

<script setup lang="ts">
import { onLoad, onShow, onHide } from '@dcloudio/uni-app'
import api from '@/config/api'
import {
  createConversation,
  messageList,
  getModelProfilePicture,
  getAiConfig,
  getCozeToken,
  createSpeech,
  getVoicesList
} from '@/api/aiChat'
import { isLogin, forceLogin } from '@/utils/filters'
import { useTabbarStore } from '@/store'
import voiceBtn from '@/pages/funcPages/components/aiChat/voiceBtn.vue'
import actions from '@/pages/funcPages/components/aiChat/actions.vue'
import storage from '@/utils/storage'
import { useMarkdownRenderer } from '@/hooks/useMarkdownRenderer'
const { renderMarkdown } = useMarkdownRenderer()

const accountInfo = uni.getAccountInfoSync()
const tabbarStore = useTabbarStore()
const tabbarList = tabbarStore.tabbarList.map((i) => i.linkUrl)
const curPageIsHaveTab = ref(false)
const chatList = ref<any>([]) // 聊天列表
const aiInfo = ref<any>({}) // AI信息
const voiceOrInput = ref(false) // true 语音 false 输入
const textareaVal = ref('') // 输入框内容
const scrollIntoView = ref('') // 滚动到指定位置
const bottomId = ref(0) // 滚动到底部
const authorization = ref('') // 访问令牌
const aiChatInfo = ref<any>(storage.getAiChatInfo() || {}) // AI聊天信息
let innerAudioContext: any = null
let audioQueue: string[] = [] // 存储待播放的音频文件路径
let isPlaying = false // 是否正在播放音频
// 当次语音播放状态
let isCurOpenSound = false
let audioSpeechQueue: Promise<void> = Promise.resolve()
const aiAnswerComplete = ref(true) // AI回答是否完成
const pageHeight = ref(1000)
//计算属性, 计算pageHeight
// const pageHeight = computed(() => {
//   const systemInfo: any = uni.getWindowInfo()
//   const pages = getCurrentPages() // 获取当前页面栈
//   const currentPage = pages[pages.length - 1] // 当前页面
//   const currentPath = `/${currentPage.route}` // 获取当前页面路径
//   curPageIsHaveTab.value = tabbarList.includes(currentPath)
//   console.log('curPageIsHaveTab:', curPageIsHaveTab.value, systemInfo)
//   // const height = curPageIsHaveTab.value
//   //   ? systemInfo.screenHeight -
//   //       ((systemInfo.screenWidth / 750) * 120 + systemInfo.screenTop + (systemInfo?.safeAreaInsets?.bottom || 0))
//   //   : systemInfo.screenHeight - systemInfo.screenTop
//   const height = curPageIsHaveTab.value
//     ? systemInfo.safeArea.height - (systemInfo.screenWidth / 750)*120 - 40
//         // ((systemInfo.screenWidth / 750) * 120 + systemInfo.screenTop + (systemInfo?.safeAreaInsets?.bottom || 0))
//     : systemInfo.safeArea.height - 40
//     console.log('pageHeight:', height)
//   return height
// })

const chatHeight = computed(() => {
  const systemInfo: any = uni.getWindowInfo()
  console.log('chatHeight:', pageHeight.value - (systemInfo.screenWidth / 750) * 280)
  return pageHeight.value - (systemInfo.screenWidth / 750) * 280
})

const accessToken = computed(() => storage.getAccessToken() || '')

function createChat() {
  uni.showModal({
    title: '对话标题',
    content: '新对话',
    editable: true,
    placeholderText: '请输入对话标题',
    confirmColor: '#FCC300',
    cancelColor: '#707175',
    success(res) {
      if (res.confirm) {
        createSession(res.content)
      } else if (res.cancel) {
        cancelCreate()
      }
    }
  })
}

// 创建新会话
function createSession(title: any) {
  if (!title.trim()) {
    uni.showToast({ title: '请输入对话标题', icon: 'none' })
    return
  }
  // 调用接口创建新会话
  uni.showLoading({ title: '创建中' })
  createConversation({
    title
  })
    .then((res) => {
      uni.hideLoading()
      storage.setAiChatInfo(res.data.result)
      aiChatInfo.value = res.data.result
      chatList.value = []
    })
    .finally(() => {})
}

function cancelCreate() {
  if (!aiChatInfo.value.conversationId) {
    // 切换tab到首页
    uni.switchTab({
      url: '/pages/index'
    })
  }
}

function getList() {
  if (!aiChatInfo.value.conversationId) {
    return
  }
  messageList({ conversationId: aiChatInfo.value.conversationId }).then((res: any) => {
    chatList.value = res.data.result.data.reverse()
    setTimeout(() => {
      scrollToBottom()
    }, 500)
  })
}

// 重新发送
function restartSend() {
  chatList.value.pop()
  sendStreamMsg(chatList.value[chatList.value.length - 1].content)
}

// 发送文字消息
function sendMsg() {
  if (!textareaVal.value) {
    return
  }
  if (!aiAnswerComplete.value) {
    uni.showToast({ title: '请等待AI回答完毕', icon: 'none' })
    return
  }
  chatList.value.push({
    role: 'user',
    content: textareaVal.value,
    loading: false,
    chat_id: new Date().getTime()
  })
  sendStreamMsg()
}

// 准备发送语音消息, 语音上传中
function sendVoice(text: string) {
  if (!aiAnswerComplete.value) {
    uni.showToast({ title: '请等待AI回答完毕', icon: 'none' })
    return
  }
  // 上传中
  chatList.value.push({
    role: 'user',
    content: text,
    loading: true,
    chat_id: new Date().getTime()
  })
  scrollToBottom()
}

// 语音上传成功回调,
function sendVoiceSuccess(msg: string) {
  if (!aiAnswerComplete.value) {
    uni.showToast({ title: '请等待AI回答完毕', icon: 'none' })
    return
  }
  const lastMessage = chatList.value[chatList.value.length - 1]
  lastMessage.loading = false
  lastMessage.content = msg
  sendStreamMsg(msg, 2)
}

// 语音上传失败回调
function sendVoiceError() {
  console.log('语音上传失败')
  chatList.value[chatList.value.length - 1].success = '0'
  chatList.value[chatList.value.length - 1].loading = false
}

// 发送流消息
function sendStreamMsg(tempMsg: any = '', type: number = 1) {
  if (!textareaVal.value && !tempMsg) {
    return
  }
  stopAudio()
  isCurOpenSound = true
  aiAnswerComplete.value = false // 没回答完
  const requestTask: any = uni.request({
    url: api.buyer + '/ai/sendStreamChat',
    method: 'POST',
    data: {
      conversationId: aiChatInfo.value.conversationId,
      message: textareaVal.value || tempMsg,
      type: type // 1 文字 2 语音
    },
    enableChunked: true,
    header: {
      'content-type': 'application/json', // 默认值
      token: accessToken.value,
      appId: accountInfo.miniProgram.appId
    },
    success(res) {},
    fail(err) {
      console.log('消息流获取失败', err)
      chatList.value[chatList.value.length - 1].success = '0'
      chatList.value[chatList.value.length - 1].loading = false
      aiAnswerComplete.value = true
    }
  })
  textareaVal.value = ''
  // 添加一条空消息, 等待AI返回
  chatList.value.push({
    role: 'assistant',
    content: '',
    loading: true
  })
  // 滚动到底部
  scrollToBottom()
  // 监听消息返回

  // 缓存返回的数据
  let cacheStr = ''
  let cacheStr2 = ''
  
  // 处理返回的数据
  function handleChunk(res: any) {
    const lastMessage = chatList.value[chatList.value.length - 1]
    if (lastMessage.loading) {
      lastMessage.loading = false
    }

    const text = utf8Decode(res.data)
    if (text.includes('Token usage')) {
      aiAnswerComplete.value = true
      requestTask.offChunkReceived(handleChunk)
      // 结束了, 最后合成一次语音
      if (cacheStr2.length > 0 && aiInfo.value.isShowOpenSound && aiInfo.value.isOpenSound) {
        const segment = cacheStr2.replace(/\*/g, '').replace(/\#/g, '')
        cacheStr2 = ''
        audioSpeechQueue = audioSpeechQueue.then(() => {
          return audioSpeech(segment).then((filePath: any) => {
            audioQueue.push(filePath)
            console.log('audioQueue11', audioQueue)
            playNextAudio()
          })
        })
      }
      return false
    }

    let tempText = ''

    const isComplete = text.endsWith('\n\n')
    if (!isComplete) {
      cacheStr += text
      return
    } else {
      tempText = cacheStr + text
      cacheStr = ''
    }
    tempText = tempText
      .replace(/\ndata:\n\n/, '|||||')
      .replace(/\n|data:/g, '')
      .replace(/\|\|\|\|\|/g, '\n\n')
    chatList.value[chatList.value.length - 1].content += tempText
    cacheStr2 += tempText
    
      const threshold = 25
      if (
        cacheStr2.length > threshold &&
        aiInfo.value.isShowOpenSound &&
        aiInfo.value.isOpenSound &&
        isCurOpenSound
      ) {
        const [segment, index] = getSegmentText(cacheStr2.replace(/\*/g, '').replace(/\#/g, ''), threshold)
        
        if (index === -1 || segment.trim() === '') return
        console.log(segment)
        cacheStr2 = cacheStr2.slice(index)
        // 将audioSpeech调用加入队列，确保同步执行
        audioSpeechQueue = audioSpeechQueue.then(() => {
          return audioSpeech(segment).then((filePath: any) => {
            audioQueue.push(filePath)
            playNextAudio()
          })
        })
      }

    setTimeout(() => {
      scrollToBottom()
    }, 100)
  }
  
  requestTask.onChunkReceived(handleChunk)
}

// 播放下一个音频
function playNextAudio() {
  if (isPlaying || audioQueue.length === 0 || !isCurOpenSound) return
  isPlaying = true
  const filePath: any = audioQueue.shift()
  console.log('当前播放: ', filePath, '剩余:', audioQueue)
  innerAudioContext.src = filePath
  innerAudioContext.play()
  
}

// 获取分段文字和index, 按标点符号分段, 以防语音卡顿明显
function getSegmentText(str: string, startIndex: number): [string, number] {
  if (str.length < startIndex) return ['', -1]
  const punctuationRegex = /[，。！？；：,!?;:]/

  // 从 startIndex 开始查找下一个标点符号
  const nextPunctuationIndex = str.slice(startIndex).search(punctuationRegex)

  if (nextPunctuationIndex !== -1) {
    // console.log('下一个标点符号的索引:', startIndex + nextPunctuationIndex)
    return [str.slice(0, startIndex + nextPunctuationIndex + 1), startIndex + nextPunctuationIndex + 1]
  } else {
    // console.log('未找到标点符号')
    return ['', -1]
  }
}

function utf8Decode(arrayBuffer: any) {
  const bytes = new Uint8Array(arrayBuffer)
  let str = ''
  let i = 0
  while (i < bytes.length) {
    const byte1 = bytes[i++]
    if (byte1 <= 0x7f) {
      str += String.fromCharCode(byte1)
    } else if (byte1 >= 0xc2 && byte1 <= 0xdf) {
      const byte2 = bytes[i++]
      str += String.fromCharCode(((byte1 & 0x1f) << 6) | (byte2 & 0x3f))
    } else if (byte1 >= 0xe0 && byte1 <= 0xef) {
      const byte2 = bytes[i++]
      const byte3 = bytes[i++]
      str += String.fromCharCode(((byte1 & 0x0f) << 12) | ((byte2 & 0x3f) << 6) | (byte3 & 0x3f))
    } else if (byte1 >= 0xf0 && byte1 <= 0xf7) {
      const byte2 = bytes[i++]
      const byte3 = bytes[i++]
      const byte4 = bytes[i++]
      const codePoint = ((byte1 & 0x07) << 18) | ((byte2 & 0x3f) << 12) | ((byte3 & 0x3f) << 6) | (byte4 & 0x3f)
      if (codePoint >= 0x10000 && codePoint <= 0x10ffff) {
        str +=
          String.fromCharCode(((codePoint - 0x10000) >>> 10) + 0xd800) +
          String.fromCharCode(((codePoint - 0x10000) & 0x3ff) + 0xdc00)
      }
    }
  }
  return str
}
// 使scroll-view中的消息列表滚动到最底部
function scrollToBottom() {
  bottomId.value++
  if (bottomId.value > 10) {
    bottomId.value = 1
  }

  scrollIntoView.value = 'bottom_' + bottomId.value
}

// 停止语音播放方法
function stopAudio() {
  if (innerAudioContext) {
    
    isPlaying = false // 更新播放状态
    isCurOpenSound = false
    audioQueue = [] // 清空音频队列
    clearFileList()
    audioSpeechQueue = Promise.resolve()
    console.log('音频已停止并清理')
    innerAudioContext.stop() // 停止播放
    // innerAudioContext.destroy() // 销毁音频实例
    // innerAudioContext = uni.createInnerAudioContext()
  }
}
function getAiInfo() {
  getModelProfilePicture().then((res: any) => {
    const { iconUrl: headUrl, name, prologue } = res.data.result
    aiInfo.value = {
      ...aiInfo.value,
      headUrl,
      name,
      prologue
    }
  })

  getAiConfig().then((res: any) => {
    aiInfo.value.isShowOpenSound = res.data.result.aiModel == 2 // 是否为语音播报
    let isOpenSound = uni.getStorageSync('isOpenSound')
    if (aiInfo.value.isShowOpenSound && !isOpenSound) {
      // 为null, 未设置过
      uni.setStorageSync('isOpenSound', 2)
      isOpenSound = 2
    }

    aiInfo.value.isOpenSound = isOpenSound == 2 // 是否开启语音播报
    if (aiInfo.value.isShowOpenSound) {
      getToken()
    }
  })
}

function openSound(isOpenSound: number) {
  console.log('切换状态', isOpenSound)
  aiInfo.value.isOpenSound = isOpenSound == 2 // 是否开启语音播报
  uni.setStorageSync('isOpenSound', isOpenSound)
  isCurOpenSound = false
    // 切换为关闭
    stopAudio()
}
function getToken() {
  getCozeToken().then((res: any) => {
    console.log('获取token', res)
    authorization.value = res.data.result
    // authorization.value = 'pat_kSnrsBj2CrY4BM0a9DaeFMA4er0Mdobckgk4xJku7Dtem7isl8U7LT4ZKPuQVFrc'
    // pat_Eb6M73qq86Lrsg0cKTny0fd6jetDVUTnkZu9XkDB8Nd8rjfis7kzeegY11eSlv1T
    
    // getVoicesList({}, authorization.value).then((res: any) => {
    //   console.log('获取语音列表', res)
    // })
  })
}

// 语音合成函数
function audioSpeech(text: string): Promise<string> {
  return new Promise((resolve, reject) => {
    uni.request({
      url: 'https://api.coze.cn/v1/audio/speech',
      method: 'POST',
      data: {
        input: text,
        voice_id: '7426720361733144585',
        response_format: 'wav',
        speed: 1.0
      },
      header: {
        'content-type': 'application/json',
        Authorization: `Bearer ${authorization.value}`
      },
      responseType: 'arraybuffer',
      success(res) {
        if (res.data instanceof ArrayBuffer) {
          const timestamp = new Date().getTime()
          const filePath = `${uni.env.USER_DATA_PATH}/audio_${timestamp}.wav`
          uni.getFileSystemManager().writeFile({
            filePath,
            data: res.data,
            encoding: 'binary',
            success: () => {
              resolve(filePath)
            },
            fail: (err) => {
              console.error('写入文件失败', err)
              reject(err)
            }
          })
        } else {
          console.error('返回的数据类型不正确')
          reject(new Error('返回的数据类型不正确'))
        }
      },
      fail(err) {
        reject(err)
      }
    })
  })
}
// 清空文件列表
function clearFileList() {
  const fileManager = uni.getFileSystemManager()
  const dirPath = uni.env.USER_DATA_PATH
  fileManager.readdir({
    dirPath,
    success: (res) => {
      res.files.forEach((file) => {
        if (file.endsWith('.wav')) {
          fileManager.unlink({
            filePath: `${dirPath}/${file}`,
            success: () => {
              // console.log('文件删除成功:', file)
            },
            fail: (err) => {
              console.error('文件删除失败:', err)
            }
          })
        }
      })
    },
    fail: (err) => {
      console.error('读取目录失败:', err)
    }
  })
}

onMounted(() => {
  if (tabbarStore.activeTabName && curPageIsHaveTab.value) {
    uni.setNavigationBarTitle({
      title: tabbarStore.activeTabName
    })
  }
  
})
onShow(() => {
  aiAnswerComplete.value = true
  innerAudioContext = uni.createInnerAudioContext()
  innerAudioContext.onEnded(() => {
    isPlaying = false
    if (audioQueue.length > 0) {
      playNextAudio()
    } else {
      // 所有音频播放完毕，重置状态
      stopAudio()
    }
      // 检查文件是否存在
      // uni.getFileSystemManager().access({
      //   path: filePath,
      //   success: () => {
      //     // 文件存在，尝试删除
      //     uni.getFileSystemManager().unlink({
      //       filePath: filePath,
      //       success: () => {
      //         console.log('文件删除成功:', filePath)
      //         // 确保在删除文件后继续播放下一条
      //         if (audioQueue.length > 0) {
      //           playNextAudio()
      //         } else {
      //           // 所有音频播放完毕，重置状态
      //           stopAudio()
      //         }
      //       },
      //       fail: (err) => {
      //         console.error('文件删除失败:', err)
      //         if (audioQueue.length > 0) {
      //           playNextAudio()
      //         }
      //       }
      //     })
      //   },
      //   fail: () => {
      //     console.log('文件不存在:', filePath)
      //     if (audioQueue.length > 0) {
      //       playNextAudio()
      //     }
      //   }
      // })
    })
  clearFileList()
  forceLogin()
  if (isLogin('auth')) {
    getAiInfo()

    if (!aiChatInfo.value.conversationId) {
      createChat()
      return
    }
    aiChatInfo.value = storage.getAiChatInfo()

    getList()
  }
})
onLoad(() => {
  const systemInfo: any = uni.getWindowInfo()
  const pages = getCurrentPages() // 获取当前页面栈
  const currentPage = pages[pages.length - 1] // 当前页面
  const currentPath = `/${currentPage.route}` // 获取当前页面路径
  curPageIsHaveTab.value = tabbarList.includes(currentPath)
  console.log('curPageIsHaveTab:', curPageIsHaveTab.value, systemInfo)
  pageHeight.value = curPageIsHaveTab.value
    ? Math.min(systemInfo.safeArea.height, systemInfo.windowHeight) - (systemInfo.screenWidth / 750)*120 - (systemInfo.safeArea.height < systemInfo.windowHeight ? 44 :0)
    : Math.min(systemInfo.safeArea.height, systemInfo.windowHeight) - (systemInfo.safeArea.height < systemInfo.windowHeight ? 40 :0)
})
onHide(() => {
  clearFileList()
  if (innerAudioContext) {
    innerAudioContext.destroy()
  }
})
</script>
<style>
page {
  height: 100%;
  overflow: hidden;
}

::-webkit-scrollbar {
  width: 0px;
  height: 0px;
}
</style>
<style scoped>
.ai-card {
  background: #ffffff;
  border-radius: 5rpx 30rpx 30rpx 30rpx;
}
.mine-card {
  background: #ffdd68;
  border-radius: 30rpx 5rpx 30rpx 30rpx;
}
.mine-nick {
  text-align: right;
}

h1 {
  font-size: 18px; /* 调整 h1 的字体大小 */
  margin: 10px 0;
}

h2 {
  font-size: 16px; /* 调整 h2 的字体大小 */
  margin: 8px 0;
}
</style>
